#!/usr/bin/env python3
"""
资产收集Agent
"""
import requests
import platform
import socket
import psutil
import json
import time
import sys
import os

# Windows XP兼容性修复
if sys.platform == "win32":
    # 解决Windows XP上可能缺少的API
    try:
        import ctypes
        from ctypes import wintypes

        # 为旧版Windows添加缺失的常量
        if not hasattr(wintypes, 'HANDLE'):
            wintypes.HANDLE = ctypes.c_void_p

        # 解决socket可能的问题
        if not hasattr(socket, 'AF_LINK'):
            socket.AF_LINK = 18

    except Exception:
        pass

# 解决Python 3.8+在旧Windows上的路径问题
if sys.version_info >= (3, 8) and sys.platform == "win32":
    os.environ["PYTHONLEGACYWINDOWSSTDIO"] = "1"

# 编码兼容性修复
if sys.platform == "win32":
    import codecs

    # 确保标准输出使用正确编码
    if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
        try:
            sys.stdout.reconfigure(encoding='utf-8')
        except:
            pass


# 多进程fork参数检查 - 必须在所有其他导入之前
if len(sys.argv) > 1 and '--multiprocessing-fork' in sys.argv[1]:
    # 这是PyInstaller创建的子进程，直接退出
    sys.exit(0)


import tempfile
from datetime import datetime
import argparse
import logging
from pathlib import Path
import tkinter as tk
from tkinter import ttk, messagebox
import threading
import pystray
from PIL import Image, ImageDraw, ImageFont


# 单实例检查 - 防止重复启动
def check_single_instance(lock_name="AssetAgent_Lock"):
    """检查是否已经有实例在运行"""
    try:
        # 在Windows上使用socket
        if hasattr(socket, 'socket'):
            global_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            try:
                global_socket.bind(('localhost', 32451))  # 使用固定端口
                return True  # 没有其他实例在运行
            except socket.error:
                return False  # 已经有实例在运行
        else:
            # 在其他平台上使用文件锁
            lock_file = os.path.join(tempfile.gettempdir(), f"{lock_name}.lock")
            if os.path.exists(lock_file):
                # 检查锁文件是否有效
                try:
                    with open(lock_file, 'r') as f:
                        pid = int(f.read().strip())
                    # 检查进程是否存在
                    import psutil
                    if psutil.pid_exists(pid):
                        return False
                except:
                    pass

            # 创建新的锁文件
            with open(lock_file, 'w') as f:
                f.write(str(os.getpid()))
            return True
    except:
        return True  # 如果检查失败，允许启动


# 在main函数开始前检查
if not check_single_instance():
    print("AssetAgent 已经在运行中")
    sys.exit(0)


class AssetAgent:
    """资产收集Agent """

    def __init__(self, config=None):
        self.config = config or self.load_config()
        self.agent_id = self.generate_agent_id()
        self.setup_logging()
        self.tray_icon = None
        self.is_running = True

        # 新增状态属性
        self.last_report_time = None
        self.last_report_status = "从未上报"
        self.total_reports = 0
        self.successful_reports = 0

        # 预导入UI模块以避免线程问题
        self._pre_import_ui_modules()

    def _pre_import_ui_modules(self):
        """预导入UI相关模块"""
        try:
            import tkinter as tk
            from tkinter import messagebox
            # 这些导入会在类实例中保持引用
            self._tk = tk
            self._messagebox = messagebox
        except Exception as e:
            self.logger.warning(f"预导入UI模块失败: {e}")

    def show_thread_safe_message(self, title, message, is_error=False):
        """线程安全的消息显示方法"""

        def show():
            try:
                import tkinter as tk
                from tkinter import messagebox

                root = tk.Tk()
                root.withdraw()
                root.attributes('-topmost', True)

                if is_error:
                    messagebox.showerror(title, message)
                else:
                    messagebox.showinfo(title, message)

                root.destroy()
            except Exception as e:
                self.logger.error(f"显示消息失败: {e}")

        threading.Thread(target=show, daemon=True).start()

    def setup_logging(self):
        """设置日志系统"""
        log_dir = os.path.join(tempfile.gettempdir(), 'AssetAgent', 'logs')
        os.makedirs(log_dir, exist_ok=True)

        # 检查是否是无控制台模式
        is_no_console = hasattr(sys, 'frozen') and not sys.stdout

        handlers = [
            logging.FileHandler(os.path.join(log_dir, 'agent.log'))
        ]

        # 只有在有控制台的情况下才添加 StreamHandler
        if not is_no_console:
            handlers.append(logging.StreamHandler())

        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=handlers
        )
        self.logger = logging.getLogger(__name__)

    def load_config(self):
        """加载配置文件"""
        default_config = {
            'server_url': 'http://localhost:5001',
            'report_interval': 259200,
            'auto_start': True
        }

        # 获取当前程序目录
        if getattr(sys, 'frozen', False):
            # 打包后的可执行文件所在目录
            base_path = os.path.dirname(sys.executable)
        else:
            # 脚本所在目录
            base_path = os.path.dirname(os.path.abspath(__file__))

        config_path = os.path.join(base_path, 'agent_config.json')
        if os.path.exists(config_path):
            try:
                with open(config_path, 'r', encoding='utf-8') as f:
                    user_config = json.load(f)
                default_config.update(user_config)
            except Exception as e:
                self.logger.error(f"加载配置文件失败: {e}")

        return default_config

    def save_config(self, config):
        """保存配置到文件"""
        # 获取当前程序目录
        if getattr(sys, 'frozen', False):
            base_path = os.path.dirname(sys.executable)
        else:
            base_path = os.path.dirname(os.path.abspath(__file__))

        config_path = os.path.join(base_path, 'agent_config.json')
        config['last_updated'] = datetime.now().isoformat()

        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=2, ensure_ascii=False)

        return config_path

    def generate_agent_id(self):
        """生成Agent ID"""
        hostname = socket.gethostname()
        return f"{hostname}-{os.getpid()}"

    def create_tray_icon(self):
        """创建系统托盘图标"""
        try:
            image = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
            dc = ImageDraw.Draw(image)

            for i in range(64):
                color = (30, 144, 255, int(255 * (i / 64)))
                dc.line([(0, i), (64, i)], fill=color)

            dc.ellipse([16, 16, 48, 48], fill=(255, 255, 255, 200), outline=(255, 255, 255))
            dc.text((32, 32), "A", fill=(30, 144, 255), font=self.get_font(20), anchor="mm")

            menu = pystray.Menu(
                pystray.MenuItem('显示状态', self.show_status),
                pystray.MenuItem('立即上报', self.force_report),
                pystray.MenuItem('关于我们', self.show_about),
                pystray.MenuItem('退出应用', self.exit_app)
            )

            self.tray_icon = pystray.Icon("asset_agent", image, "资产收集Agent", menu)
        except Exception as e:
            self.logger.error(f"创建托盘图标失败: {e}")

    def get_font(self, size):
        """获取字体"""
        try:
            return ImageFont.truetype("arial.ttf", size)
        except:
            return ImageFont.load_default()

    def show_status(self, icon=None, item=None):
        """显示状态信息"""
        try:
            def show_dialog():
                root = tk.Tk()
                root.withdraw()
                root.attributes('-topmost', True)
                messagebox.showinfo("Agent状态",
                                    f"服务器: {self.config['server_url']}\n"
                                    f"Agent ID: {self.agent_id}\n"
                                    f"运行中...")
                root.destroy()

            threading.Thread(target=show_dialog, daemon=True).start()
        except Exception as e:
            self.logger.error(f"显示状态失败: {e}")

    def force_report(self, icon=None, item=None):
        """立即上报"""
        try:
            def report_and_show():
                try:
                    success = self.report_to_server()
                    status = "成功" if success else "失败"
                    self._show_message_in_main_thread(f"资产上报{status}")
                except Exception as e:
                    self.logger.error(f"上报过程中出错: {e}")
                    self._show_message_in_main_thread("上报过程出错")

            thread = threading.Thread(target=report_and_show, daemon=True)
            thread.start()

        except Exception as e:
            self.logger.error(f"立即上报失败: {e}")

    def _show_message_in_main_thread(self, message):
        """在主线程中安全显示消息"""
        try:
            import tkinter as tk
            from tkinter import messagebox

            root = tk.Tk()
            root.withdraw()
            root.attributes('-topmost', True)
            messagebox.showinfo("立即上报", message)
            root.destroy()
        except Exception as e:
            self.logger.error(f"显示消息失败: {e}")

    def show_about(self, icon=None, item=None):
        """显示关于对话框"""
        try:
            def show_about_dialog():
                root = tk.Tk()
                root.title("关于资产收集Agent")
                root.resizable(True, True)

                # 设置窗口居中
                root.update_idletasks()
                width = 450
                height = 520
                x = (root.winfo_screenwidth() // 2) - (width // 2)
                y = (root.winfo_screenheight() // 2) - (height // 2)
                root.geometry(f"{width}x{height}+{x}+{y}")

                # 创建主容器框架
                main_container = ttk.Frame(root, padding="15")
                main_container.pack(fill=tk.BOTH, expand=True)

                main_container.columnconfigure(0, weight=1)

                # 1. 标题区域
                title_frame = ttk.Frame(main_container)
                title_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))

                title_label = ttk.Label(title_frame, text="资产收集Agent",
                                        font=("微软雅黑", 16, "bold"), foreground="#2c3e50")
                title_label.pack()

                version_label = ttk.Label(title_frame, text="版本 2.0",
                                          font=("微软雅黑", 10), foreground="#7f8c8d")
                version_label.pack()

                # 2. 功能特性区域
                features_frame = ttk.LabelFrame(main_container, text="功能特性", padding="10")
                features_frame.grid(row=1, column=0, sticky="ew", pady=5)

                features = [
                    "✅ 自动收集系统硬件信息",
                    "✅ 定时上报资产数据到服务器",
                    "✅ 系统托盘后台运行",
                    "✅ 开机自动启动",
                    "✅ 支持自定义配置"
                ]

                for i, feature in enumerate(features):
                    ttk.Label(features_frame, text=feature, font=("微软雅黑", 9)).grid(
                        row=i, column=0, sticky="w", pady=2)

                # 3. 状态信息区域
                status_frame = ttk.LabelFrame(main_container, text="当前状态", padding="10")
                status_frame.grid(row=3, column=0, sticky="ew", pady=5)

                status_data = [
                    ("服务器:", self.config.get('server_url', '未设置')),
                    ("Agent ID:", self.agent_id),
                    ("运行模式:", '正常模式'),
                    ("最后上报:", getattr(self, 'last_report_time', '从未上报'))
                ]

                for i, (label, value) in enumerate(status_data):
                    ttk.Label(status_frame, text=label, font=("微软雅黑", 9, "bold")).grid(
                        row=i, column=0, sticky="w", padx=(0, 5))
                    ttk.Label(status_frame, text=value, font=("微软雅黑", 9)).grid(
                        row=i, column=1, sticky="w")

                # 上报状态显示
                report_status = getattr(self, 'last_report_status', '未知')
                status_color = "#27ae60" if report_status == "成功" else "#e74c3c" if report_status == "失败" else "#f39c12"

                status_label = ttk.Label(status_frame, text=f"上报状态: {report_status}",
                                         font=("微软雅黑", 10, "bold"), foreground=status_color)
                status_label.grid(row=len(status_data), column=0, columnspan=2, pady=(5, 0))

                # 4. 按钮区域
                button_frame = ttk.Frame(main_container)
                button_frame.grid(row=4, column=0, sticky="ew", pady=(15, 5))

                button_container = ttk.Frame(button_frame)
                button_container.pack(expand=True)

                ttk.Button(button_container, text="立即上报",
                           command=lambda: self.force_report_and_update(root),
                           style="Accent.TButton").pack(side=tk.LEFT, padx=5)

                ttk.Button(button_container, text="确定",
                           command=root.destroy, width=10).pack(side=tk.LEFT, padx=5)

                # 添加分隔线
                separator = ttk.Separator(main_container, orient='horizontal')
                separator.grid(row=5, column=0, sticky="ew", pady=10)

                # 版权信息
                copyright_label = ttk.Label(main_container, text="© 2024 资产收集Agent 版权所有",
                                            font=("微软雅黑", 8), foreground="#95a5a6")
                copyright_label.grid(row=6, column=0)

                root.mainloop()

            threading.Thread(target=show_about_dialog, daemon=True).start()

        except Exception as e:
            self.logger.error(f"显示关于对话框失败: {e}")
            self.show_simple_about()

    def show_simple_about(self):
        """显示简化版关于信息"""
        try:
            def show_simple():
                root = tk.Tk()
                root.withdraw()
                root.attributes('-topmost', True)

                last_report = getattr(self, 'last_report_time', '从未上报')
                report_status = getattr(self, 'last_report_status', '未知')

                about_info = f"""资产收集Agent 版本 2.0

📊 当前状态:
• 服务器: {self.config.get('server_url', '未设置')}
• Agent ID: {self.agent_id}
• 最后上报: {last_report}
• 上报状态: {report_status}

✨ 主要功能:
• 自动收集系统硬件信息
• 定时上报资产数据
• 系统托盘后台运行"""

                messagebox.showinfo("关于资产收集Agent", about_info)
                root.destroy()

            threading.Thread(target=show_simple, daemon=True).start()
        except Exception as e:
            self.logger.error(f"显示简化关于信息失败: {e}")

    def exit_app(self, icon, item):
        """退出应用"""
        self.is_running = False
        if self.tray_icon:
            try:
                self.tray_icon.stop()
            except Exception as e:
                self.logger.error(f"停止托盘图标失败: {e}")

        self.logger.info("Agent正在退出...")
        os._exit(0)

    def collect_asset_info(self):
        """收集资产信息"""
        hw_info = self.get_hardware_info()
        system_info = self.get_system_info()  # 获取更新后的系统信息

        return {
            'host_identifier': self.agent_id,
            'name': socket.gethostname(),
            'specifications': {
                'hardware_info': hw_info,
                'system_info': self.get_system_info(),
                'network_info': self.get_network_info()
            },
            'cpu_info': json.dumps(hw_info.get('cpu', {})),
            'memory_info': json.dumps(hw_info.get('memory', {})),
            'disk_info': json.dumps(hw_info.get('disks', [])),
            'os_info': json.dumps(self.get_system_info()),
            'network_info': json.dumps(self.get_network_info()),
            'serial_number': hw_info.get('serial_number', ''),
            'agent_id': self.agent_id,
            'agent_version': '2.0',
            'report_time': datetime.now().isoformat()
        }

    def get_hardware_info(self):
        """获取硬件信息 - 避免使用外部命令行工具"""
        try:
            # CPU信息 - 使用纯Python实现，避免cpuinfo
            cpu_info = self._get_detailed_cpu_info()

            memory = psutil.virtual_memory()
            memory_info = {
                'total': memory.total,
                'available': memory.available,
                'used': memory.used,
                'percent': memory.percent
            }

            disks = []
            for partition in psutil.disk_partitions():
                try:
                    usage = psutil.disk_usage(partition.mountpoint)
                    disks.append({
                        'device': partition.device,
                        'mountpoint': partition.mountpoint,
                        'total': usage.total,
                        'used': usage.used,
                        'free': usage.free,
                        'percent': usage.percent
                    })
                except PermissionError:
                    continue

            mac_info = self.get_mac_info()
            user_info = self.get_user_info()

            return {
                'cpu': cpu_info,
                'memory': memory_info,
                'disks': disks,
                'serial_number': self.get_serial_number(),
                'mac_info': mac_info,
                'user_info': user_info
            }
        except Exception as e:
            self.logger.error(f"获取硬件信息失败: {e}")
            return {}

    def get_mac_info(self):
        """获取MAC地址信息"""
        try:
            mac_addresses = {}
            for interface, addrs in psutil.net_if_addrs().items():
                if interface.startswith(('lo', 'virbr', 'docker', 'veth')):
                    continue

                for addr in addrs:
                    if addr.family == psutil.AF_LINK:
                        if addr.address and addr.address != '00:00:00:00:00:00':
                            mac_addresses[interface] = addr.address
                            break
            return mac_addresses
        except Exception as e:
            self.logger.error(f"获取MAC信息失败: {e}")
            return {}

    def get_user_info(self):
        """获取当前登录用户信息"""
        try:
            users = psutil.users()
            user_list = []
            for user in users:
                user_list.append({
                    'name': user.name,
                    'terminal': user.terminal,
                    'host': user.host,
                    'started': user.started,
                    'pid': user.pid
                })
            return user_list
        except Exception as e:
            self.logger.error(f"获取用户信息失败: {e}")
            return []

    def get_serial_number(self):
        """获取系统序列号 - 彻底解决黑框问题"""
        try:
            system = platform.system()

            if system == 'Windows':
                # 方法1：优先使用WMI（无黑框）
                try:
                    import wmi
                    c = wmi.WMI()
                    for bios in c.Win32_ComputerSystemProduct():
                        serial = bios.IdentifyingNumber
                        if serial and serial.strip() and serial != 'None':
                            return serial.strip()
                except ImportError:
                    pass  # WMI不可用，继续尝试其他方法

                # 方法2：使用注册表（无黑框）
                try:
                    import winreg
                    try:
                        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                                             r"SYSTEM\CurrentControlSet\Services\Disk\Enum")
                        serial, _ = winreg.QueryValueEx(key, "0")
                        winreg.CloseKey(key)
                        if serial and serial.strip():
                            return serial.strip()
                    except:
                        pass
                except ImportError:
                    pass

                # 方法3：使用subprocess并彻底隐藏窗口（最后手段）
                try:
                    import subprocess
                    import ctypes

                    # 更彻底的窗口隐藏
                    startupinfo = subprocess.STARTUPINFO()
                    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                    startupinfo.wShowWindow = 0  # SW_HIDE

                    # 设置进程创建标志，避免创建控制台窗口
                    creationflags = subprocess.CREATE_NO_WINDOW

                    result = subprocess.check_output(
                        ['wmic', 'csproduct', 'get', 'uuid'],
                        startupinfo=startupinfo,
                        stderr=subprocess.DEVNULL,
                        stdin=subprocess.DEVNULL,
                        creationflags=creationflags,
                        timeout=5  # 5秒超时
                    )
                    lines = result.decode('utf-8', errors='ignore').split('\n')
                    for line in lines:
                        line = line.strip()
                        if line and 'UUID' not in line:
                            return line
                except:
                    pass

                return 'Unknown'

            elif system == 'Linux':
                # Linux方法不变
                try:
                    with open('/sys/class/dmi/id/product_uuid', 'r') as f:
                        return f.read().strip()
                except:
                    try:
                        import subprocess
                        result = subprocess.check_output(
                            ['dmidecode', '-s', 'system-uuid'],
                            stderr=subprocess.DEVNULL
                        )
                        return result.decode().strip()
                    except:
                        return 'Unknown'

            elif system == 'Darwin':
                # macOS方法不变
                try:
                    import subprocess
                    result = subprocess.check_output(
                        ['ioreg', '-l', '-d2', '-c', 'IOPlatformExpertDevice'],
                        stderr=subprocess.DEVNULL
                    )
                    for line in result.decode().split('\n'):
                        if '"IOPlatformSerialNumber"' in line:
                            return line.split('=')[-1].strip().strip('"')
                except:
                    return 'Unknown'

            return 'Unknown'

        except Exception as e:
            self.logger.error(f"获取序列号失败: {e}")
            return 'Error'

    def get_system_info(self):
        """获取系统信息"""
        try:
            system_info = {
                'system': platform.system(),
                'node': platform.node(),
                'release': platform.release(),
                'version': platform.version(),
                'machine': platform.machine(),
                'processor': platform.processor(),
                'architecture': platform.architecture()[0],
                'python_version': platform.python_version()
            }

            # Windows系统详细版本检测 - 改进版本
            if platform.system() == 'Windows':
                windows_info = self._get_windows_version_info()
                system_info.update(windows_info)

                # 确保os_description字段存在
                if 'os_description' not in system_info:
                    system_info['os_description'] = windows_info.get('windows_product_name',
                                                                     f"Windows {platform.release()}")

            return system_info
        except Exception as e:
            self.logger.error(f"获取系统信息失败: {e}")
            return {}

    def _get_windows_version_info(self):
        """获取Windows版本信息 - 无黑框版本"""
        try:
            # 方法1：使用注册表获取更详细的信息
            try:
                import winreg
                key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                                     r"SOFTWARE\Microsoft\Windows NT\CurrentVersion")

                product_name = winreg.QueryValueEx(key, "ProductName")[0]
                edition_id = winreg.QueryValueEx(key, "EditionID")[0] if "EditionID" in [winreg.EnumValue(key, i)[0] for
                                                                                         i in range(
                        winreg.QueryInfoKey(key)[1])] else ""
                installation_type = winreg.QueryValueEx(key, "InstallationType")[0] if "InstallationType" in [
                    winreg.EnumValue(key, i)[0] for i in range(winreg.QueryInfoKey(key)[1])] else ""
                current_build = winreg.QueryValueEx(key, "CurrentBuild")[0]
                build_number = winreg.QueryValueEx(key, "CurrentBuildNumber")[0]
                release_id = winreg.QueryValueEx(key, "ReleaseId")[0] if "ReleaseId" in [winreg.EnumValue(key, i)[0] for
                                                                                         i in range(
                        winreg.QueryInfoKey(key)[1])] else ""

                winreg.CloseKey(key)

                # 根据注册表信息判断是否为服务器版本
                is_server = any(keyword in product_name.lower() for keyword in
                                ['server', 'datacenter', 'standard']) or installation_type.lower() == 'server'

                if is_server:
                    # 服务器版本的特殊处理
                    server_version = self._get_windows_server_version(product_name, edition_id, current_build,
                                                                      build_number)
                    return {
                        'windows_product_name': server_version,
                        'windows_build': f"{current_build}.{build_number}",
                        'os_description': server_version,
                        'is_server': True
                    }
                else:
                    # 桌面版本
                    desktop_version = self._get_windows_desktop_version(product_name, release_id, current_build,
                                                                        build_number)
                    return {
                        'windows_product_name': desktop_version,
                        'windows_build': f"{current_build}.{build_number}",
                        'os_description': desktop_version,
                        'is_server': False
                    }

            except Exception as e:
                self.logger.debug(f"注册表获取Windows版本失败: {e}")

            # 方法2：使用platform模块和更精确的版本映射
            return self._get_windows_version_from_platform()

        except Exception as e:
            self.logger.debug(f"获取Windows版本信息失败: {e}")
            return {'os_description': f"Windows {platform.release()}"}

    def _get_windows_server_version(self, product_name, edition_id, current_build, build_number):
        """获取Windows服务器版本信息"""
        try:
            # Windows Server版本映射
            server_versions = {
                '10.0.20348': 'Windows Server 2022',
                '10.0.17763': 'Windows Server 2019',
                '10.0.14393': 'Windows Server 2016',
                '10.0.9200': 'Windows Server 2012 R2',
                '10.0.8400': 'Windows Server 2012',
                '6.3.9600': 'Windows Server 2012 R2',
                '6.2.9200': 'Windows Server 2012',
                '6.1.7601': 'Windows Server 2008 R2',
                '6.1.7600': 'Windows Server 2008',
                '6.0.6003': 'Windows Server 2008',
                '5.2.3790': 'Windows Server 2003'
            }

            full_build = f"{current_build}.{build_number}"

            # 优先使用注册表的产品名称
            if 'server' in product_name.lower():
                return product_name

            # 其次使用版本映射
            if full_build in server_versions:
                return server_versions[full_build]

            # 最后根据版本号推断
            if build_number >= 20348:
                return "Windows Server 2022"
            elif build_number >= 17763:
                return "Windows Server 2019"
            elif build_number >= 14393:
                return "Windows Server 2016"
            elif build_number >= 9200:
                return "Windows Server 2012 R2"
            else:
                return f"Windows Server {platform.release()}"

        except Exception as e:
            self.logger.debug(f"获取Windows服务器版本失败: {e}")
            return "Windows Server"

    def _get_windows_desktop_version(self, product_name, release_id, current_build, build_number):
        """获取Windows桌面版本信息"""
        try:
            # 优先使用注册表的产品名称
            if product_name and 'windows' in product_name.lower():
                return product_name

            # 桌面版本映射
            desktop_versions = {
                '10.0.22621': 'Windows 11 Version 22H2',
                '10.0.22000': 'Windows 11 Version 21H2',
                '10.0.19045': 'Windows 10 Version 22H2',
                '10.0.19044': 'Windows 10 Version 21H2',
                '10.0.19043': 'Windows 10 Version 21H1',
                '10.0.19042': 'Windows 10 Version 20H2',
                '10.0.19041': 'Windows 10 Version 2004',
                '10.0.18363': 'Windows 10 Version 1909',
                '10.0.18362': 'Windows 10 Version 1903',
                '10.0.17763': 'Windows 10 Version 1809',
                '10.0.17134': 'Windows 10 Version 1803',
                '10.0.16299': 'Windows 10 Version 1709',
                '10.0.15063': 'Windows 10 Version 1703',
                '10.0.14393': 'Windows 10 Version 1607',
                '10.0.10586': 'Windows 10 Version 1511',
                '10.0.10240': 'Windows 10 Version 1507',
                '6.3.9600': 'Windows 8.1',
                '6.2.9200': 'Windows 8',
                '6.1.7601': 'Windows 7 SP1',
                '6.1.7600': 'Windows 7',
                '6.0.6001': 'Windows Vista SP1',
                '6.0.6000': 'Windows Vista',
                '5.1.2600': 'Windows XP'
            }

            full_build = f"{current_build}.{build_number}"

            if full_build in desktop_versions:
                return desktop_versions[full_build]

            # 使用Release ID（如果可用）
            if release_id:
                return f"Windows 10 Version {release_id}"

            # 根据版本号推断
            if build_number >= 22000:
                return "Windows 11"
            elif build_number >= 10240:
                return "Windows 10"
            elif build_number >= 9200:
                return "Windows 8.1"
            elif build_number >= 7600:
                return "Windows 7"
            else:
                return f"Windows {platform.release()}"

        except Exception as e:
            self.logger.debug(f"获取Windows桌面版本失败: {e}")
            return "Windows"

    def _get_windows_version_from_platform(self):
        """通过platform模块推断Windows版本 - 改进版本"""
        try:
            version = platform.version()
            release = platform.release()
            platform_str = platform.platform()

            # 检查是否为服务器版本
            is_server = any(
                keyword in platform_str.lower() for keyword in ['server', '-server-', 'datacenter', 'standard'])

            # Windows版本映射 - 区分服务器和桌面版本
            windows_versions = {
                '10.0.20348': 'Windows Server 2022' if is_server else 'Windows 11',
                '10.0.17763': 'Windows Server 2019' if is_server else 'Windows 10 Version 1809',
                '10.0.14393': 'Windows Server 2016' if is_server else 'Windows 10 Version 1607',
                '10.0.9200': 'Windows Server 2012 R2' if is_server else 'Windows 8.1',
                '10.0.8400': 'Windows Server 2012' if is_server else 'Windows 8'
            }

            if version in windows_versions:
                os_name = windows_versions[version]
            elif is_server:
                os_name = f"Windows Server {release}"
            else:
                os_name = f"Windows {release}"

            # 添加更详细的版本信息
            if is_server and 'server' not in os_name.lower():
                os_name = f"Windows Server {release}"

            return {
                'windows_product_name': os_name,
                'os_description': os_name,
                'is_server': is_server
            }
        except Exception as e:
            self.logger.debug(f"通过platform推断Windows版本失败: {e}")
            return {'os_description': f"Windows {platform.release()}"}

    def _get_detailed_cpu_info(self):
        """获取详细的CPU信息 - 无黑框版本"""
        try:
            cpu_info = {
                'brand_raw': platform.processor(),
                'arch': platform.architecture()[0],
                'bits': platform.architecture()[0],
                'cores': psutil.cpu_count(logical=False),
                'logical_cores': psutil.cpu_count(logical=True),
                'usage': psutil.cpu_percent(interval=1),
                'frequency': psutil.cpu_freq().current if psutil.cpu_freq() else None,
            }

            # 跨平台CPU信息获取 - 不使用WMI/WMIC
            detailed_name = self._get_cpu_name_cross_platform()
            if detailed_name:
                cpu_info['name'] = detailed_name
                cpu_info['brand_raw'] = detailed_name

            return cpu_info

        except Exception as e:
            self.logger.error(f"获取详细CPU信息失败: {e}")
            return {
                'brand_raw': platform.processor(),
                'cores': psutil.cpu_count(logical=False),
                'logical_cores': psutil.cpu_count(logical=True),
            }

    def _get_cpu_name_cross_platform(self):
        """跨平台获取CPU名称 - 无黑框"""
        try:
            system = platform.system()

            if system == 'Windows':
                # Windows: 使用注册表获取CPU信息
                return self._get_cpu_info_from_registry()

            elif system == 'Linux':
                # Linux: 从/proc/cpuinfo读取
                return self._get_cpu_info_from_proc()

            elif system == 'Darwin':
                # macOS: 使用sysctl
                return self._get_cpu_info_from_sysctl()

        except Exception as e:
            self.logger.debug(f"获取CPU名称失败: {e}")

        return None

    def _get_cpu_info_from_registry(self):
        """从Windows注册表获取CPU信息"""
        try:
            import winreg
            # 尝试多个注册表路径
            registry_paths = [
                r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
                r"HARDWARE\DESCRIPTION\System\CentralProcessor\0\Configuration"
            ]

            for path in registry_paths:
                try:
                    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
                    try:
                        # 尝试获取处理器名称
                        processor_name, _ = winreg.QueryValueEx(key, "ProcessorNameString")
                        if processor_name and processor_name.strip():
                            return processor_name.strip()
                    except FileNotFoundError:
                        pass
                    winreg.CloseKey(key)
                except Exception:
                    continue

        except Exception as e:
            self.logger.debug(f"注册表获取CPU信息失败: {e}")

        return None

    def _get_cpu_info_from_proc(self):
        """从/proc/cpuinfo获取CPU信息(Linux)"""
        try:
            with open('/proc/cpuinfo', 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
                for line in content.split('\n'):
                    if 'model name' in line.lower():
                        return line.split(':')[1].strip()
        except Exception as e:
            self.logger.debug(f"/proc/cpuinfo获取CPU信息失败: {e}")

        return None

    def _get_cpu_info_from_sysctl(self):
        """从sysctl获取CPU信息(macOS)"""
        try:
            import subprocess
            result = subprocess.run(['sysctl', '-n', 'machdep.cpu.brand_string'],
                                    capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return result.stdout.strip()
        except Exception as e:
            self.logger.debug(f"sysctl获取CPU信息失败: {e}")

        return None


    def get_network_info(self):
        """获取网络信息"""
        try:
            network_info = {
                'hostname': socket.gethostname(),
                'ip_address': socket.gethostbyname(socket.gethostname()),
                'network_interfaces': {}
            }

            for interface, addrs in psutil.net_if_addrs().items():
                network_info['network_interfaces'][interface] = []
                for addr in addrs:
                    network_info['network_interfaces'][interface].append({
                        'family': addr.family.name if hasattr(addr.family, 'name') else str(addr.family),
                        'address': addr.address,
                        'netmask': addr.netmask,
                        'broadcast': addr.broadcast
                    })

            net_io = psutil.net_io_counters()
            network_info['network_stats'] = {
                'bytes_sent': net_io.bytes_sent,
                'bytes_recv': net_io.bytes_recv,
                'packets_sent': net_io.packets_sent,
                'packets_recv': net_io.packets_recv,
                'errin': net_io.errin,
                'errout': net_io.errout,
                'dropin': net_io.dropin,
                'dropout': net_io.dropout
            }

            return network_info

        except Exception as e:
            self.logger.error(f"获取网络信息失败: {e}")
            return {}

    def report_to_server(self):
        """向服务器上报资产信息"""
        try:
            asset_data = self.collect_asset_info()
            current_time = datetime.now()

            clean_asset_data = {
                'host_identifier': asset_data.get('host_identifier'),
                'name': asset_data.get('name'),
                'specifications': asset_data.get('specifications'),
                'cpu_info': asset_data.get('cpu_info'),
                'memory_info': asset_data.get('memory_info'),
                'disk_info': asset_data.get('disk_info'),
                'os_info': asset_data.get('os_info'),
                'network_info': asset_data.get('network_info'),
                'serial_number': asset_data.get('serial_number'),
                'agent_id': asset_data.get('agent_id'),
                'report_time': current_time.isoformat()
            }

            api_url = f"{self.config['server_url']}/asset/api/assets/report"
            self.logger.info(f"上报到: {api_url}")

            response = requests.post(
                api_url,
                json=clean_asset_data,
                headers={'Content-Type': 'application/json'},
                timeout=30
            )

            self.total_reports += 1

            if response.status_code == 200:
                result = response.json()
                self.logger.info(f"上报成功: {result.get('message')}")
                self.last_report_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
                self.last_report_status = "成功"
                self.successful_reports += 1
                return True
            else:
                self.logger.error(f"上报失败: HTTP {response.status_code} - {response.text}")
                self.last_report_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
                self.last_report_status = f"失败 (HTTP {response.status_code})"
                return False

        except Exception as e:
            self.logger.error(f"上报过程中发生错误: {e}")
            current_time = datetime.now()
            self.last_report_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
            self.last_report_status = f"失败 ({str(e)})"
            self.total_reports += 1
            return False

    def force_report_and_update(self, parent_window):
        """立即上报并更新界面显示 - 使用线程安全方法"""

        def report_and_update():
            success = self.report_to_server()

            if success:
                self.show_thread_safe_message(
                    "上报成功",
                    f"资产上报成功！\n时间: {self.last_report_time}"
                )
            else:
                self.show_thread_safe_message(
                    "上报失败",
                    f"资产上报失败！\n错误: {self.last_report_status}",
                    is_error=True
                )

        threading.Thread(target=report_and_update, daemon=True).start()

    def run_background(self):
        """后台运行 - 修复版本"""
        self.logger.info("启动资产收集Agent...")

        # 创建系统托盘图标
        self.create_tray_icon()

        # 启动托盘图标在单独的线程中
        if self.tray_icon:
            def run_tray():
                try:
                    self.logger.info("启动系统托盘图标")
                    self.tray_icon.run()
                    self.logger.info("系统托盘图标已退出")
                except Exception as e:
                    self.logger.error(f"托盘图标运行失败: {e}")

            tray_thread = threading.Thread(target=run_tray, daemon=True)
            tray_thread.start()
            self.logger.info("托盘线程已启动")

        # 立即进行一次上报
        try:
            self.logger.info("执行首次资产上报")
            success = self.report_to_server()
            if success:
                self.logger.info("首次资产上报成功")
            else:
                self.logger.warning("首次资产上报失败")
        except Exception as e:
            self.logger.error(f"首次资产上报错误: {e}")

        # 使用更安全的主循环
        self._run_main_loop()

    def _run_main_loop(self):
        """安全的主循环实现"""
        report_count = 0
        while self.is_running:
            try:
                if report_count > 0:
                    self.logger.info(f"等待{self.config['report_interval']}秒后进行下一次上报")
                    # 使用更细粒度的睡眠以便及时响应退出信号
                    for i in range(self.config['report_interval']):
                        if not self.is_running:
                            break
                        time.sleep(1)

                if not self.is_running:
                    break

                success = self.report_to_server()
                report_count += 1

                if success:
                    self.logger.info(f"第{report_count}次上报成功")
                else:
                    self.logger.warning(f"第{report_count}次上报失败")
                    # 失败后等待60秒再继续，但允许及时退出
                    for i in range(60):
                        if not self.is_running:
                            break
                        time.sleep(1)

            except KeyboardInterrupt:
                self.logger.info("收到键盘中断信号")
                break
            except Exception as e:
                self.logger.error(f"主循环错误: {e}")
                # 错误后等待60秒
                for i in range(60):
                    if not self.is_running:
                        break
                    time.sleep(1)

        self.logger.info("Agent主循环已退出")

    def execute_hidden_command(self, cmd, shell=False, timeout=10):
        """执行命令并完全隐藏窗口"""
        try:
            import subprocess

            startupinfo = None
            creationflags = 0

            if platform.system() == 'Windows':
                startupinfo = subprocess.STARTUPINFO()
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                startupinfo.wShowWindow = 0
                creationflags = subprocess.CREATE_NO_WINDOW

            result = subprocess.run(
                cmd,
                startupinfo=startupinfo,
                creationflags=creationflags,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                stdin=subprocess.DEVNULL,
                shell=shell,
                timeout=timeout,
                check=True
            )
            return result.stdout.decode('utf-8', errors='ignore').strip()
        except Exception as e:
            self.logger.debug(f"执行隐藏命令失败: {e}")
            return None



class ConfigWindow:
    """配置窗口 -  专用"""

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("资产收集Agent配置 ")
        self.root.geometry("400x200")
        self.root.resizable(False, False)

        try:
            self.root.iconbitmap(default=self.get_icon_path())
        except:
            pass

        self.setup_logging()
        self.center_window()
        self.setup_ui()

    def setup_logging(self):
        """设置日志系统"""
        log_dir = os.path.join(tempfile.gettempdir(), 'AssetAgent', 'logs')
        os.makedirs(log_dir, exist_ok=True)

        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(os.path.join(log_dir, 'config_window.log')),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)

    def get_icon_path(self):
        """获取图标路径"""
        if getattr(sys, 'frozen', False):
            return os.path.join(tempfile.gettempdir(), 'AssetAgent', 'icon.ico')
        else:
            return "icon.ico"

    def center_window(self):
        """窗口居中"""
        self.root.update_idletasks()
        width = self.root.winfo_width()
        height = self.root.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f'{width}x{height}+{x}+{y}')

    def setup_ui(self):
        """设置UI"""
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        ttk.Label(main_frame, text="服务器地址:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.server_var = tk.StringVar(value="http://localhost:5001")
        server_entry = ttk.Entry(main_frame, textvariable=self.server_var, width=30)
        server_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5, padx=(10, 0))

        ttk.Label(main_frame, text="报告间隔(秒):").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.interval_var = tk.StringVar(value="259200")
        interval_entry = ttk.Entry(main_frame, textvariable=self.interval_var, width=30)
        interval_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5, padx=(10, 0))



        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=3, column=0, columnspan=2, pady=20)

        ttk.Button(button_frame, text="保存并运行",
                   command=self.save_and_run).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="仅保存",
                   command=self.save_only).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="退出",
                   command=self.exit_app).pack(side=tk.LEFT, padx=10)

        main_frame.columnconfigure(1, weight=1)
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)

    def save_and_run(self):
        """保存并运行 - 修复版本"""
        if not self.validate_input():
            return

        # 保存配置
        config = self.save_config()

        # 显示启动消息
        messagebox.showinfo("启动", "Agent正在启动...")

        # 完全退出配置窗口
        self.root.quit()
        self.root.destroy()

        # 在新的进程中启动Agent
        try:
            agent = AssetAgent(config)
            agent.run_background()
        except Exception as e:
            self.logger.error(f"启动Agent失败: {e}")
            # 如果启动失败，可以重新显示配置窗口
            # 这里需要重新创建配置窗口实例

    def save_only(self):
        """仅保存"""
        if self.validate_input():
            self.save_config()
            messagebox.showinfo("成功", "配置已保存！")

    def exit_app(self):
        """退出"""
        self.root.quit()
        self.root.destroy()

    def validate_input(self):
        """验证输入"""
        server = self.server_var.get().strip()
        if not server:
            messagebox.showerror("错误", "请输入服务器地址！")
            return False

        try:
            interval = int(self.interval_var.get())
            if interval < 60:
                messagebox.showerror("错误", "报告间隔不能小于60秒！")
                return False
        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字！")
            return False

        return True

    def save_config(self):
        """保存配置"""
        config = {
            'server_url': self.server_var.get().strip(),
            'report_interval': int(self.interval_var.get()),
            'auto_start': True
        }

        agent = AssetAgent()
        agent.save_config(config)
        return config

    def run(self):
        """运行窗口"""
        self.root.mainloop()


def main():
    """主函数 - 简化修复版本"""
    # 过滤掉PyInstaller的多进程参数
    original_argv = sys.argv.copy()
    filtered_argv = [arg for arg in sys.argv if not arg.startswith('--multiprocessing-fork')]
    sys.argv = filtered_argv

    # 检查是否是无控制台模式
    is_no_console = hasattr(sys, 'frozen') and not sys.stdout

    # 如果是无控制台模式，使用简化的参数解析
    if is_no_console:
        minimized = False
        # 检查命令行参数
        if '--minimized' in sys.argv:
            minimized = True
        if '--debug' in sys.argv:
            # 设置调试日志
            log_dir = os.path.join(tempfile.gettempdir(), 'AssetAgent', 'logs')
            os.makedirs(log_dir, exist_ok=True)
            logging.basicConfig(
                level=logging.DEBUG,
                format='%(asctime)s - %(levelname)s - %(message)s',
                handlers=[logging.FileHandler(os.path.join(log_dir, 'agent.log'))]
            )
    else:
        # 正常模式使用 argparse
        parser = argparse.ArgumentParser(description='资产收集Agent')
        parser.add_argument('--minimized', action='store_true', help='最小化启动')
        parser.add_argument('--debug', action='store_true', help='调试模式')

        try:
            args = parser.parse_args()
            minimized = args.minimized
        except SystemExit:
            # 当遇到无法识别的参数时，使用默认值继续运行
            minimized = False

    # 启动逻辑
    if minimized:
        agent = AssetAgent()
        agent.run_background()
    else:
        app = ConfigWindow()
        app.run()


if __name__ == '__main__':
    main()